{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Building Semantic Memory with Embeddings\n",
    "\n",
    "In this notebook, we show how to use [Chroma](https://www.trychroma.com/) with Semantic Kernel to create even more\n",
    "intelligent applications. We assume that you are already familiar with the concepts of Semantic Kernel\n",
    "and memory. [Previously](04-context-variables-chat.ipynb), we have used `context variables` to pass\n",
    "additional text into prompts, enriching them with more context for a basic chat experience.\n",
    "\n",
    "However, relying solely on context variables has its limitations, such as the model's token limit.\n",
    "To overcome these limitations, we will use **SK Semantic Memory**, leveraging Chroma as a persistent\n",
    "Semantic Memory Storage.\n",
    "\n",
    "**Chroma** is an open-source embedding database designed to make it easy to build Language Model\n",
    "applications by making knowledge, facts, and plugins pluggable for LLMs. It allows us to store and\n",
    "retrieve information in a way that can be easily utilized by the models, enabling both short-term\n",
    "and long-term memory for more advanced applications. In this notebook, we will showcase how to\n",
    "effectively use Chroma with the Semantic Kernel for a powerful application experience.\n",
    "\n",
    "**Note:** This example is verified using Chroma version **0.4.10**. Any higher versions may introduce incompatibility."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "#r \"nuget: Microsoft.SemanticKernel, 1.0.0-beta6\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Connectors.Memory.Chroma, 1.0.0-beta6\"\n",
    "#r \"nuget: System.Linq.Async, 6.0.1\"\n",
    "\n",
    "#!import config/Settings.cs\n",
    "\n",
    "using System;\n",
    "using System.Collections.Generic;\n",
    "using System.Linq;\n",
    "using System.Threading.Tasks;\n",
    "using Microsoft.SemanticKernel;\n",
    "using Microsoft.SemanticKernel.Connectors.Memory.Chroma;\n",
    "using Microsoft.SemanticKernel.Memory;\n",
    "using Microsoft.SemanticKernel.Plugins.Memory;\n",
    "\n",
    "var kernelBuilder = new KernelBuilder();\n",
    "\n",
    "// Configure AI backend used by the kernel\n",
    "var (useAzureOpenAI, model, azureEndpoint, apiKey, orgId) = Settings.LoadFromFile();\n",
    "\n",
    "if (useAzureOpenAI)\n",
    "    kernelBuilder.WithAzureOpenAIChatCompletionService(model, azureEndpoint, apiKey);\n",
    "else\n",
    "    kernelBuilder.WithOpenAIChatCompletionService(model, apiKey, orgId);\n",
    "\n",
    "var kernel = kernelBuilder.Build();"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to use memory, we need to instantiate the Memory Plugin with a Memory Storage\n",
    "and an Embedding backend. In this example, we make use of the `ChromaMemoryStore`,\n",
    "leveraging [Chroma](https://www.trychroma.com/), an open source embedding database\n",
    "you can run locally and in the cloud.\n",
    "\n",
    "To run Chroma locally, here's a quick script to download Chroma source and run it using Docker:\n",
    "\n",
    "```shell\n",
    "git clone https://github.com/chroma-core/chroma.git\n",
    "cd chroma\n",
    "docker-compose up --build\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using Microsoft.SemanticKernel.Connectors.AI.OpenAI;\n",
    "\n",
    "var (useAzureOpenAI, model, azureEndpoint, apiKey, orgId) = Settings.LoadFromFile();\n",
    "\n",
    "var memoryBuilder = new MemoryBuilder();\n",
    "\n",
    "if (useAzureOpenAI)\n",
    "{\n",
    "    memoryBuilder.WithAzureOpenAITextEmbeddingGenerationService(\"text-embedding-ada-002\", azureEndpoint, apiKey);\n",
    "}\n",
    "else\n",
    "{\n",
    "    memoryBuilder.WithOpenAITextEmbeddingGenerationService(\"text-embedding-ada-002\", apiKey);\n",
    "}\n",
    "\n",
    "var chromaMemoryStore = new ChromaMemoryStore(\"http://127.0.0.1:8000\");\n",
    "\n",
    "memoryBuilder.WithMemoryStore(chromaMemoryStore);\n",
    "\n",
    "var memory = memoryBuilder.Build();"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At its core, Semantic Memory is a set of data structures that allows to store\n",
    "the meaning of text that come from different data sources, and optionally to\n",
    "store the source text and other metadata.\n",
    "\n",
    "The text can be from the web, e-mail providers, chats, a database, or from your\n",
    "local directory, and are hooked up to the Semantic Kernel through memory connectors.\n",
    "\n",
    "The texts are embedded, sort of \"compressed\", into a vector of floats that representing\n",
    "mathematically the text content and meaning.\n",
    "\n",
    "You can read more about embeddings [here](https://aka.ms/sk/embeddings)."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Manually adding memories\n",
    "\n",
    "Let's create some initial memories \"About Me\". We can add memories to `ChromaMemoryStore` by using `SaveInformationAsync`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "const string MemoryCollectionName = \"aboutMe\";\n",
    "\n",
    "await memory.SaveInformationAsync(MemoryCollectionName, id: \"info1\", text: \"My name is Andrea\");\n",
    "await memory.SaveInformationAsync(MemoryCollectionName, id: \"info2\", text: \"I currently work as a tourist operator\");\n",
    "await memory.SaveInformationAsync(MemoryCollectionName, id: \"info3\", text: \"I currently live in Seattle and have been living there since 2005\");\n",
    "await memory.SaveInformationAsync(MemoryCollectionName, id: \"info4\", text: \"I visited France and Italy five times since 2015\");\n",
    "await memory.SaveInformationAsync(MemoryCollectionName, id: \"info5\", text: \"My family is from New York\");"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try searching the memory:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "var questions = new[]\n",
    "{\n",
    "    \"what is my name?\",\n",
    "    \"where do I live?\",\n",
    "    \"where is my family from?\",\n",
    "    \"where have I travelled?\",\n",
    "    \"what do I do for work?\",\n",
    "};\n",
    "\n",
    "foreach (var q in questions)\n",
    "{\n",
    "    var response = await memory.SearchAsync(MemoryCollectionName, q, limit: 1, minRelevanceScore: 0.5).FirstOrDefaultAsync();\n",
    "    Console.WriteLine(q + \" \" + response?.Metadata.Text);\n",
    "}"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's now revisit our chat sample from the [previous notebook](04-context-variables-chat.ipynb).\n",
    "If you remember, we used context variables to fill the prompt with a `history` that continuously got populated as we chatted with the bot. Let's add also memory to it!"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is done by using the `TextMemoryPlugin` which exposes the `recall` native function.\n",
    "\n",
    "`recall` takes an input ask and performs a similarity search on the contents that have\n",
    "been embedded in the Memory Store. By default, `recall` returns the most relevant memory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using Microsoft.SemanticKernel.Plugins.Memory;\n",
    "\n",
    "// TextMemoryPlugin provides the \"recall\" function\n",
    "kernel.ImportFunctions(new TextMemoryPlugin(memory));"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "const string skPrompt = @\"\n",
    "ChatBot can have a conversation with you about any topic.\n",
    "It can give explicit instructions or say 'I don't know' if it does not have an answer.\n",
    "\n",
    "Information about me, from previous conversations:\n",
    "- {{$fact1}} {{recall $fact1}}\n",
    "- {{$fact2}} {{recall $fact2}}\n",
    "- {{$fact3}} {{recall $fact3}}\n",
    "- {{$fact4}} {{recall $fact4}}\n",
    "- {{$fact5}} {{recall $fact5}}\n",
    "\n",
    "Chat:\n",
    "{{$history}}\n",
    "User: {{$userInput}}\n",
    "ChatBot: \";\n",
    "\n",
    "var chatFunction = kernel.CreateSemanticFunction(skPrompt, requestSettings: new OpenAIRequestSettings { MaxTokens = 200, Temperature = 0.8 });"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `RelevanceParam` is used in memory search and is a measure of the relevance score from 0.0 to 1.0, where 1.0 means a perfect match. We encourage users to experiment with different values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "var context = kernel.CreateNewContext();\n",
    "\n",
    "context.Variables[\"fact1\"] = \"what is my name?\";\n",
    "context.Variables[\"fact2\"] = \"where do I live?\";\n",
    "context.Variables[\"fact3\"] = \"where is my family from?\";\n",
    "context.Variables[\"fact4\"] = \"where have I travelled?\";\n",
    "context.Variables[\"fact5\"] = \"what do I do for work?\";\n",
    "\n",
    "context.Variables[TextMemoryPlugin.CollectionParam] = MemoryCollectionName;\n",
    "context.Variables[TextMemoryPlugin.RelevanceParam] = \"0.6\";"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we've included our memories, let's chat!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "var history = \"\";\n",
    "context.Variables[\"history\"] = history;\n",
    "Func<string, Task> Chat = async (string input) => {\n",
    "    // Save new message in the context variables\n",
    "    context.Variables[\"userInput\"] = input;\n",
    "\n",
    "    // Process the user message and get an answer\n",
    "    var answer = await chatFunction.InvokeAsync(context);\n",
    "\n",
    "    // Append the new interaction to the chat history\n",
    "    history += $\"\\nUser: {input}\\nChatBot: {answer.GetValue<string>()}\\n\";\n",
    "    context.Variables[\"history\"] = history;\n",
    "    \n",
    "    // Show the bot response\n",
    "    Console.WriteLine(\"ChatBot: \" + context);\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "await Chat(\"Hello, I think we've met before, remember? my name is...\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "await Chat(\"I want to plan a trip and visit my family. Do you know where that is?\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "await Chat(\"Great! What are some fun things to do there?\");"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Adding documents to your memory\n",
    "\n",
    "Many times in your applications you'll want to bring in external documents into your memory. Let's see how we can do this using ChromaMemoryStore.\n",
    "\n",
    "Let's first get some data using some of the links in the Semantic Kernel repo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "const string memoryCollectionName = \"SKGitHub\";\n",
    "\n",
    "var githubFiles = new Dictionary<string, string>()\n",
    "{\n",
    "    [\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"]\n",
    "        = \"README: Installation, getting started, and how to contribute\",\n",
    "    [\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"]\n",
    "        = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\",\n",
    "    [\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"]\n",
    "        = \"Jupyter notebook describing how to get started with the Semantic Kernel\",\n",
    "    [\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"]\n",
    "        = \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\",\n",
    "    [\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/Plugins/Plugins.Memory/VolatileMemoryStore.cs\"]\n",
    "        = \"C# class that defines a volatile embedding store\",\n",
    "};"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's build a new Memory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "var memoryBuilder = new MemoryBuilder();\n",
    "\n",
    "if (useAzureOpenAI)\n",
    "{\n",
    "    memoryBuilder.WithAzureOpenAITextEmbeddingGenerationService(\"text-embedding-ada-002\", azureEndpoint, apiKey);\n",
    "}\n",
    "else\n",
    "{\n",
    "    memoryBuilder.WithOpenAITextEmbeddingGenerationService(\"text-embedding-ada-002\", apiKey);\n",
    "}\n",
    "\n",
    "var chromaMemoryStore = new ChromaMemoryStore(\"http://127.0.0.1:8000\");\n",
    "\n",
    "memoryBuilder.WithMemoryStore(chromaMemoryStore);\n",
    "\n",
    "var memory = memoryBuilder.Build();"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's add these files to ChromaMemoryStore using `SaveReferenceAsync`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "Console.WriteLine(\"Adding some GitHub file URLs and their descriptions to Chroma Semantic Memory.\");\n",
    "var i = 0;\n",
    "foreach (var entry in githubFiles)\n",
    "{\n",
    "    await memory.SaveReferenceAsync(\n",
    "        collection: memoryCollectionName,\n",
    "        description: entry.Value,\n",
    "        text: entry.Value,\n",
    "        externalId: entry.Key,\n",
    "        externalSourceName: \"GitHub\"\n",
    "    );\n",
    "    Console.WriteLine($\"  URL {++i} saved\");\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "string ask = \"I love Jupyter notebooks, how should I get started?\";\n",
    "Console.WriteLine(\"===========================\\n\" +\n",
    "                    \"Query: \" + ask + \"\\n\");\n",
    "\n",
    "var memories = memory.SearchAsync(memoryCollectionName, ask, limit: 5, minRelevanceScore: 0.6);\n",
    "\n",
    "i = 0;\n",
    "await foreach (var memory in memories)\n",
    "{\n",
    "    Console.WriteLine($\"Result {++i}:\");\n",
    "    Console.WriteLine(\"  URL:     : \" + memory.Metadata.Id);\n",
    "    Console.WriteLine(\"  Title    : \" + memory.Metadata.Description);\n",
    "    Console.WriteLine(\"  Relevance: \" + memory.Relevance);\n",
    "    Console.WriteLine();\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "polyglot-notebook"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
